#include <tenacitas_periodicity/date.h>

using namespace tenacitas;

// ++++++++++++++++++++++++++
// day

const basic::fault::code day::error_day_greater_31 ( 10 ) ;


const basic::fault::code day::error_day_equals_0 ( 20 ) ;



day::
day(const day::value_type & value) throw ( basic::fault ) {

    if (value > 31)
        throw (create_fault ( day, day::error_day_greater_31));

    if (value == 0)
        throw (create_fault(day, day::error_day_equals_0));

    m_value = value;

}

day::
day (const day & d) :
    m_value(d.m_value) {}

day::
~day() {}


const day::value_type &
day::
get_value() const {
    return m_value;
}

bool
day::
operator == ( const day & d ) const {
    return (m_value == d.m_value);
}

bool
day::
operator != ( const day & d ) const {
    return (m_value != d.m_value);
}

bool
day::
operator > ( const day & d ) const {
    return (m_value > d.m_value);
}

bool
day::
operator >= ( const day & d ) const  {
    return (m_value >= d.m_value);
}

bool
day::
operator < ( const day & d ) const {
    return (m_value < d.m_value);
}

bool
day::
operator <= ( const day & d ) const {
    return (m_value < d.m_value);
}

day &
day::
operator = ( const day & d ) {
    if (this != & d) {
        m_value = d.m_value;
    }
    return *this;
}

// ++++++++++++++++++++++++++
// month

const basic::fault::code month::error_month_greater_12 = 10;

const basic::fault::code month::error_month_equals_0 = 20;

const month month::jan (1);
const month month::feb (2);
const month month::mar (3);
const month month::abr (4);
const month month::may (5);
const month month::jun (6);
const month month::jul (7);
const month month::ago (8);
const month month::sep (9);
const month month::oct (10);
const month month::nov (11);
const month month::dec (12);

month::
month (const month::value_type & value) throw ( basic::fault ) {

    if (value > 12)
        throw (create_fault(month,
                            month::error_month_greater_12));

    if (value == 0)
        throw (create_fault(month,
                            month::error_month_equals_0));

    m_value = value;
}

month::
month ( const month & m ) :
    m_value(m.m_value) {}

month::
~month() {}

bool
month::
operator == ( const month & m ) const {
    return (m_value == m.m_value);
}

bool
month::
operator != ( const month & m ) const {
    return (m_value != m.m_value);
}

bool
month::
operator > ( const month & m ) const {
    return (m_value > m.m_value);
}

bool
month::
operator >= ( const month & m ) const  {
    return (m_value >= m.m_value);
}

bool
month::
operator < ( const month & m ) const {
    return (m_value < m.m_value);
}

bool
month::
operator <= ( const month & m ) const {
    return (m_value < m.m_value);
}

month &
month::
operator = ( const month & m ) {
    if (this != & m) {
        m_value = m.m_value;
    }
    return *this;
}

const month::value_type &
month::
get_value() const {
    return m_value;
}


// ++++++++++++++++++++++++++
// year

const basic::fault::code year::error_year_less_than_min ( 10 ) ;

const year::value_type year::min_value = 1582;


year::
year (const year::value_type & value) throw ( basic::fault ) {

    if (value < year::min_value)
        throw (create_fault(month,
                            year::error_year_less_than_min));

    m_value =  value;

}

year::
year (const year & y) :
    m_value(y.m_value) {}

year::
~year() {}

bool
year::
operator == ( const year & y ) const {
    return (m_value == y.m_value);
}

bool
year::
operator != ( const year & y ) const {
    return (m_value != y.m_value);
}

bool
year::
operator > ( const year & y ) const {
    return (m_value > y.m_value);
}

bool
year::
operator >= ( const year & y ) const  {
    return (m_value >= y.m_value);
}

bool
year::
operator < ( const year & y ) const {
    return (m_value < y.m_value);
}

bool
year::
operator <= ( const year & y ) const {
    return (m_value < y.m_value);
}

year &
year::
operator = ( const year & y ) {
    if (this != & y) {
        m_value = y.m_value;
    }
    return *this;
}

year
year::
operator + (const basic::i16t & n) const {
    return year(m_value + n);
}

year
year::
operator - (const basic::i16t & n) const {
    return year(m_value - n);
}

const year::value_type &
year::
get_value() const {
    return m_value;
}


// ++++++++++++++++++++++++++
// weekday

const basic::fault::code weekday::error_weekday_greater_6 ( 10 ) ;


const weekday weekday::sun (0);
const weekday weekday::mon (1);
const weekday weekday::tue (2);
const weekday weekday::wen (3);
const weekday weekday::thu (4);
const weekday weekday::fri (5);
const weekday weekday::sat (6);

weekday::
weekday (const weekday::value_type & value) throw ( basic::fault ) {

    if (value > 6)
        throw (create_fault(weekday,
                            weekday::error_weekday_greater_6));
    m_value = value;
}

weekday::
weekday (const weekday & m) :
    m_value(m.m_value) {}

weekday::
~weekday() {}

bool
weekday::
operator == ( const weekday & w ) const {
    return (m_value == w.m_value);
}

bool
weekday::
operator != ( const weekday & w ) const {
    return (m_value != w.m_value);
}

bool
weekday::
operator > ( const weekday & w ) const {
    return (m_value > w.m_value);
}

bool
weekday::
operator >= ( const weekday & w ) const  {
    return (m_value >= w.m_value);
}

bool
weekday::
operator < ( const weekday & w ) const {
    return (m_value < w.m_value);
}

bool
weekday::
operator <= ( const weekday & w ) const {
    return (m_value < w.m_value);
}

weekday &
weekday::
operator = ( const weekday & w ) {
    if ( this != & w ) {
        m_value = w.m_value;
    }
    return *this;
}

const weekday::value_type &
weekday::
get_value() const {
    return m_value;
}


// ++++++++++++++++++++++++++
// date

const basic::fault::code date::error_date_previous_to_min_date ( 10 ) ;


const basic::fault::code date::error_invalid_date ( 20 ) ;

const basic::fault::code date::error_processing_date ( 30 ) ;

// corresponds to 1582-oct-1
const date::value_type date::min_value = 578027;



date::
date() :
    m_value(date::min_value) {

    date::to_date(m_value, m_day, m_month, m_year);

    //     m_weekday = calculate_weekday();
}

date::
date ( const day & d ,
       const month & m ,
       const year & y) throw ( basic::fault ) {

    value_type vaux = date::to_number(d, m, y);
    validate(d, m, y, vaux);
    m_day = d;
    m_month = m;
    m_year = y;
    m_value = vaux;

    //     m_weekday = calculate_weekday();
}


date::
date (const date & d) :
    m_day(d.m_day),
    m_month(d.m_month),
    m_year(d.m_year),
    m_value(d.m_value)// ,
  // m_weekday(calculate_weekday()
  // )
{}

date::
~date() {}

const day  &
date::
get_day() const {
    return m_day;
}

const month &
date::
get_month() const {
    return m_month;
}

const year &
date::
get_year() const {
    return m_year;
}


weekday
date::
get_weekday() {
    return calculate_weekday();
}

void
date::
set_day ( const day & d ) throw ( basic::fault ) {
    date::value_type v = to_number ( d, m_month, m_year ) ;
    validate ( d, m_month, m_year, v ) ;
    m_day = d;
    m_value = v;
}


void
date::
set_month ( const month & m ) throw ( basic::fault ) {
    date::value_type v = to_number ( m_day, m, m_year ) ;
    validate ( m_day, m, m_year, v ) ;
    m_month = m;
    m_value = v;
}

void
date::
set_year ( const year & y ) throw ( basic::fault ) {
    date::value_type v = to_number ( m_day, m_month, y ) ;
    validate ( m_day, m_month, y, v ) ;
    m_year = y;
    m_value = v;
}


bool
date::
operator == ( const date & d ) const {
    return (m_value == d.m_value);
}

bool
date::
operator != ( const date & d ) const {
    return (m_value != d.m_value);
}

bool
date::
operator > ( const date & d ) const {
    return (m_value > d.m_value);
}

bool
date::
operator >= ( const date & d ) const  {
    return (m_value >= d.m_value);
}

bool
date::
operator < ( const date & d ) const {
    return (m_value < d.m_value);
}

bool
date::
operator <= ( const date & d ) const {
    return (m_value < d.m_value);
}

date &
date::
operator = ( const date & d ) {
    if (this != & d) {
        m_day = d.m_day;
        m_month = d.m_month;
        m_year = d.m_year;
        m_value = d.m_value;

        // m_weekday = d.m_weekday;
    }

    return *this;
}

date::
date (const date::value_type & date_as_number)
throw ( basic::fault ) {

    day d;
    month m;
    year y;
    to_date(date_as_number, d, m, y);
    validate(d, m, y, date_as_number);

    m_day = d;
    m_month = m;
    m_year = y;
    m_value = date_as_number;

    //     m_weekday = calculate_weekday();
}

void
date::
validate( const day & d ,
          const month & m ,
          const year & y,
          const date::value_type & v) throw ( basic::fault ) {

    if (v < date::min_value)
        throw (create_fault("date",
                            date::error_date_previous_to_min_date));

    day d_aux;
    month m_aux;
    year y_aux;
    date::to_date(v, d_aux, m_aux, y_aux);

    if (!(y_aux == y && m_aux == m && d_aux == d)) {
        throw (create_fault(date, date::error_invalid_date));
    }
}

date::value_type
date::
to_number( const day & d ,
		   const month & m ,
		   const year & y) {

    basic::umaxt maux= static_cast<basic::umaxt>((m.get_value() + 9) % 12);
    /* mar=0, feb=11 */

    basic::umaxt yaux = static_cast<basic::umaxt>(y.get_value()) -
            static_cast<basic::umaxt>( maux/10 );

    date::value_type
            vaux (
                static_cast<basic::umaxt>(
                    365 * yaux                                +
                    static_cast<basic::umaxt>( yaux / 4 )       -
                    static_cast<basic::umaxt>( yaux / 100 )     +
                    static_cast<basic::umaxt>( yaux / 400 )     +
                    static_cast<basic::umaxt>( (maux * 306 + 5)
                                        / 10 )           +
                    d.get_value()                             -
                    1));
    return vaux;
    // long gday(struct sdate d) {       /* convert date to day number */
    //   long  y, m;

    //   m = (d.m + 9)%12;                /* mar=0, feb=11 */
    //   y = d.y - m/10;                     /* if Jan/Feb, year-- */
    //   return y*365 + y/4 - y/100 + y/400 + (m*306 + 5)/10 + (d.d - 1);
    //   }
}

void
date::
to_date(const date::value_type & v,
        day & d, month & m, year & y)  {

    basic::umaxt yaux = static_cast<basic::umaxt>((10000 * v + 14780) /
                                    3652425);
    basic::i64t ddd = v -
            (yaux * 365                   +
             static_cast<basic::i64t>(yaux / 4)   -
             static_cast<basic::i64t>(yaux / 100) +
             static_cast<basic::i64t>(yaux / 400));

    if (ddd < 0) {
        --yaux;
        ddd = v -
                (yaux * 365                   +
                 static_cast<basic::i64t>(yaux / 4)   -
                 static_cast<basic::i64t>(yaux / 100) +
                 static_cast<basic::i64t>(yaux / 400));
    }

    basic::u16t maux = static_cast<basic::u16t>( (52 + 100 * ddd) / 3060 );
    y = year(static_cast<basic::u16t>(yaux) +
             static_cast<basic::u16t>( (maux + 2)/12 ));
    m = month(static_cast<basic::u8t>((maux + 2) % 12 + 1));
    d = day(static_cast<basic::u8t>(ddd -
                                     static_cast<basic::imaxt>((maux * 306 +
                                                                 5)/10) + 1));


    //   cout << "m_day = " << static_cast<u16t>(m_day);
    //   cout << ", m_month = " << m_month;
    //   cout << ", m_year = " << m_year << endl;


    // struct sdate dtf(long d) { /* convert day number to y,m,d format */
    //   struct sdate pd;
    //   long y, ddd, mm, dd, mi;

    //   y = (10000*d + 14780)/3652425;
    //   ddd = d - (y*365 + y/4 - y/100 + y/400);
    //   if (ddd < 0) {
    //     y--;
    //     ddd = d - (y*365 + y/4 - y/100 + y/400);
    //     }
    //   mi = (52 + 100*ddd)/3060;
    //   pd.y = y + (mi + 2)/12;
    //   pd.m = (mi + 2)%12 + 1;
    //   pd.d = ddd - (mi*306 + 5)/10 + 1;
    //   return pd;
    //   }

}


const date::value_type &
date::
get_value() const {
    return m_value;
}

date
date::
today() {
    struct timeval now;
    struct tm today, *ptoday = 0;

    ptoday = & today;
    gettimeofday(& now, (struct timezone *)NULL);
    ptoday = localtime(& now.tv_sec);

    date rc (
                day(static_cast<day::value_type>(ptoday->tm_mday)),
                month(static_cast<month::value_type>(ptoday->tm_mon + 1)),
                year(static_cast<year::value_type>(ptoday->tm_year + 1900)));

    return rc;
}



weekday
date::
calculate_weekday() {

    // Before you start

    // You will need to remember four numbers associated with this date.
    // The day of the month (which we shall call D), the month (M),
    // the two-digit year (Y) and the century (C).

    // In our example, D = 28, M = 4 (April), Y = 99 and C = 19.

    // Oh, there's one small complication here - our formula works on what are
    // called 'March years' - years which begin in the month of March.
    // If your chosen date is in January or February then you need to consider
    // these as month 13 or month 14 in the previous year. For example,
    // 27 February, 1961 would occur in the 'fourteenth' month of 1960, so
    // its values would be D = 27, M = 14, Y = 60 and C = 19.
    basic::imaxt
            s1 = 0, s2 = 0, s3 = 0, s4 = 0, s5 = 0, s = 0,
            D = 0, M = 0, Y = 0, C = 0;

    D = static_cast<basic::imaxt>(m_day.get_value());
    M = static_cast<basic::imaxt>(m_month.get_value());
    Y = static_cast<basic::imaxt>(m_year.get_value());

    if ( (m_month == month::jan) || (m_month == month::feb) ) {
        --Y;
        M += 12;
    }

    C = static_cast<basic::umaxt>(Y/100.00);
    //  cout << "C == " << C << endl;

    Y -= C * 100;
    //  cout << "Y == " << Y << endl;


    // Step One

    // The first calculation is on the century, C. Divide it by four
    // (ignoring the remainder), then subtract twice the century from this,
    // and then subtract a further one from the result.

    //     S1 = C/4 - 2*C - 1

    s1 = static_cast<basic::imaxt> (C / 4) - (2 * C) - 1;
    //  cout << "s1 = " << s1 << endl;

    s += s1;
    //  cout << "s = " << s << endl;

    // In our example, S1 = 19/4 - 2*19 - 1, which comes to minus 35.

    // Remember this number - you'll need it later.

    // Step Two

    // Next we calculate using the year, Y. Multiply it by five, then divide
    // it by four, again ignoring the remainder.

    //     S2 = 5*Y/4

    s2 = static_cast<basic::imaxt>(5 * Y / 4);
    //  cout << "s2 = " << s2 << endl;

    // So we have: S2 = 5*99 / 4, which comes to 123.

    // You need to add this to the number you found in step one,
    // and keep a running total.

    // We have minus 35 plus 123, which gives us 88.

    s += s2;
    //  cout << "s = " << s << endl;

    // Step Three

    // Add one to the month, M, multiply it by 26, then divide the lot by ten,
    // ignoring the remainder.

    //     S3 = 26*(M+1)/10

    s3 =  static_cast<basic::imaxt> ( 26 * (M + 1) / 10 );
    //  cout << "s3 = " << s3 << endl;

    // For our example we have 26 * (4+1) / 10, which comes to 13.

    // Once again, add this to our running total:

    // 88 + 13 = 101.

    s += s3;
    //  cout << "s = " << s << endl;

    // Step Four

    // The easiest step - just add the day, D, to the running total:

    //     S4 = D

    // 101 + 28 = 129

    s4 = D;
    //  cout << "s4 = " << s4 << endl;

    s += s4;
    //  cout << "s = " << s << endl;

    // Step 5

    // Divide our running total by seven, but this time we're only interested
    // in the remainder. This will be a number between 0 and 6.

    // For our example: 129/7 = 18 remainder 3. Our result is 3.


    s5 = s % 7;
    //  cout << "s5 = " << s5 << endl;

    if (s5 < 0) {
        s5 += 7;
        //    cout << "s5 new = " << s5 << endl;
    }

    return weekday(static_cast<weekday::value_type>(s5));
}




// date
// tenacitas::
// next_month( const date & d ) {

//   date rc;

//   if (d.get_month() == date::dec)
//     rc = date(d.get_day(), date::jan, d.get_year() + 1);
//   else
//     rc = date(d.get_day(), date::month(static_cast<u16t>(d.get_month()) + 1),
//               d.get_year());

//   return rc;
// }
